<?
/**
 * Klasa abstrakcyjna ModelMapper wzorzec projektowy Data Mapper
 * @param DB_Conn $DB - przechowuje obiekt bazy danych
 * @param string $findQuery - zapytanie o konkretny obiekt
 * @param string $updateQuery - zapytanie aktualizujace obiekt
 * @param string $insertQuery - zapytanie tworzace obiekt
 * @param string $deleteQuery - zapytanie usuwajace obiekt
 * @param string $findAllQuery - zapytanie zwracajace wszystkie obiekty danego modelu
 * @param integer $cacheTime - czas cache`owania zapytania jesli chcemy - ustalamy w konkretnym mapperze
 * @param ModelMapper $instance - singleton+factorymethod
 * zapytania rozwijane sa w klasach pochodnych
 * klasa mappera tworzona jest na podstawie polaczonego wzorca singleton z factory method
 *
 */
abstract class ModelMapper{
	protected static $DB;
	protected $tableName;
	protected $selectViewName;
	protected $selectFields;
	protected $findQuery;
	protected $updateQuery;
	protected $insertQuery;
	protected $deleteQuery;
	protected $findAllQuery;
	protected $findNextIdQuery;
	protected $findPreviousQuery;
	protected $maxIntValue = '2147483647';
	protected $mssqlIdentityQuery = "SELECT scope_identity() as id";
	protected $cacheTime = null;
	protected $defaultCacheTime = 60;
	private static $instance;

	/**
	 * konstruktor sprawdza czy zostal juz przypisany obiekt bazy do zmiennej $DB, jesli nie to jest on przypisywany
	 *
	 */
	function __construct(){
		if (!self::$DB){
			self::$DB = Database::instance();
		}
	}

	/**
	 * Metoda getCacheTime - zwraca ustawiony cacheTime - potrzebne przy kolekcjach rozszerzonych
	 *
	 * @return int $cacheTime
	 */
	function getCacheTime(){
		return $this->cacheTime;
	}

	function setCacheTime($time){
		return $this->cacheTime = $time;
	}
	/**
	 * Metoda instance singleton+factory method - zwraca obiekt mappera na podstawie nazwy klasy modelu
	 *
	 * @param string $className
	 * @return ModelMapper
	 */
	static function instance($className){
		if (!isset(self::$instance[$className])) {
			if (!class_exists($className)) {
				throw new Exception ('Nie odnaleziono klasy mappera "' . $className . '"');
			}
			self::$instance[$className] = FactoryHelper::getFinder($className, 'Mapper');
		}
		//debug(self::$instance);
		return self::$instance[$className];
	}

	/**
	 * Metoda korzysta z statycznej metody exists klasy ObjectWatcher - mapy tożsamosci
	 * sprawdza czy zadany obiekt jest juz zachowany na mapie, jesli tak to zwraca obiekt
	 *
	 * @param integer $id
	 * @return null or DomainModel
	 */
	function getFromMap($id){
		return ObjectWatcher::exists(str_replace('Mapper', '', get_class($this)), $id);
	}

	/**
	 * Metoda addToMap korzysta z statycznej metody klasy ObjectWatcher, sluzy do utrwalania obiektu modelu na mapie tozsamosci
	 *
	 * @param DomainModel $obj
	 */
	function addToMap(DomainModel $obj){
		ObjectWatcher::add($obj);
	}

	/**
	 * Metoda load przyjmuje recordset jako parametr, na podstawie recordset tworzy obiekt wywolujac metode loadArray
	 *
	 * @param ADORecordSet $result
	 * @return DomainModel $object
	 */
	function load(ADORecordSet $result){
		//debug($result->fields);
		if (!is_array($result->fields)){
			return null;
		}
		if (!$result->fields['id']){
			return null;
		}
		$object = $this->loadArray($result->fields);
		return $object;
	}

	/**
	 * metoda loadAll analogicznie do metody load przyjmuje recordset jako parametr
	 * zwraca tablice obiektow wytworzonych metoda loadArray
	 *
	 * @param ADORecordSet $result
	 * @return array DomainModel
	 */
	function loadAll(ADORecordSet $result){
		//debug($result);
		if (!is_array($result->fields)){
			return null;
		}
		if (!$result->fields['id']){
			return null;
		}
		while ($row = $result->FetchRow()) {
			$object[] = $this->loadArray($result->fields);
		}

		return $object;
	}

	/**
	 * Metoda loadArray na podstawie tablicy wyniku przekazanej przez metody load, loadAll
	 * zwraca obiekt pobrany z mapy, lub utworzony jesli nie istnieje na mapie
	 *
	 * @param result $array
	 * @return DomainModel $object
	 */
	function loadArray($array){
		if ($array['id'] == '')
			return null;
		$oldObj = $this->getFromMap($array['id']);
		
		if ($oldObj){
			return $oldObj;
		}
		//debug($array);
		$object = $this->doLoad($array);
		
		$this->addToMap($object);
		
		$object->markClean();
		return $object;
	}

	/**
	 * Metoda find sluzy do zbudowania obiektu na podstawie identyfikatora
	 * zwraca obiekt z mapy lub uruchamia metode doFind jesli takowego na mapie nie znajdzie
	 *
	 * @param integer $id
	 * @return DomainModel
	 */
	function find($id){
		
		$oldObj = $this->getFromMap(intval($id));
		//debug($oldObj);
		if ($oldObj && $oldObj->getReloadParam() == false){
			return $oldObj;
		}

		return $this->doFind(intval($id));
	}
	
	function findOneFiltered($filter, $fields = '', $fromView = true){
		return $this->doFindOneFiltered($filter, $fields, $fromView);
	}

	/**
	 * ONLY FOR MSSQL DBASE WITH PAGER PROCEDURE DEFINIED
	 *
	 * @param unknown_type $filter
	 * @param unknown_type $orderBy
	 * @param unknown_type $limit
	 * @param unknown_type $offset
	 * @param unknown_type $fields
	 * @return unknown
	 */
	function findFilteredPagedQuery($filter = '', $orderBy = '', $limit = 100, $offset = 0, $fields = '', $fromView = true){
		if ($fromView == false){
			$from = $this->tableName;
		} else {
			$from = $this->selectViewName;
		}
		
		if ($fields == ''){
			$fields = $this->selectFields;
		}
		
		if ($orderBy == ''){
			$orderBy = 'ORDER BY id ASC';
		}
		
		if (intval($limit) == 0)
			$limit = 100;
		
		$query = "
			SELECT ".$fields."
			FROM ".$from."
			".$filter."
			".$orderBy."
			LIMIT ".$limit."
			OFFSET ".$offset."
		";
		
		return self::$DB->prepare($query);
		//return self::$DB->prepare("EXECUTE dbo.pager @query = ".self::$DB->qstr($query).", @orderBy = ".self::$DB->qstr($orderBy).", @limit = ".intval($limit).", @offset = ".intval($offset).", @r_count=0;");
	}
	
	/**
	 * ONLY FOR MSSQL DBASE WITH PAGER PROCEDURE DEFINIED
	 *
	 * @param unknown_type $filter
	 * @param unknown_type $orderBy
	 * @param unknown_type $limit
	 * @param unknown_type $offset
	 * @param unknown_type $fields
	 * @return unknown
	 */
	function findFilteredQuery($filter = '', $orderBy = '', $limit = 100, $offset = 0, $fields = '', $fromView = true){
		if ($fromView == false){
			$from = $this->tableName;
		} else {
			$from = $this->selectViewName;
		}
		
		if ($fields == ''){
			$fields = $this->selectFields;
		}
		
		if ($orderBy == ''){
			$orderBy = 'ORDER BY id ASC';
		}
		
		if (intval($limit) == 0)
			$limit = 100;
		
		$query = "
			SELECT ".$fields."
			FROM ".$from."
			".$filter."
			".$orderBy."
		";
		
		return self::$DB->prepare($query);
	}
	
	function findOneFilteredQuery($filter, $fields = '', $fromView = true){
		if ($fromView == false){
			$from = $this->tableName;
		} else {
			$from = $this->selectViewName;
		}
		
		if ($fields == ''){
			$fields = $this->selectFields;
		}
		
		if ($filter == ''){
			$filter = "WHERE id = 0";
		}
		
		$query = "
			SELECT ".$fields."
			FROM ".$from."
			".$filter."
			LIMIT 1
		";
		
		return self::$DB->prepare($query);
	}
	
	/**
	 * Zwraca kolekcje obiektow na podstawie zapytania findAllQuery
	 *
	 * @return ModelCollection
	 */
	function findFiltered($filter = '', $orderBy = '', $limit = null, $offset = null, $fields = ''){
		$result = $this->doStatement($this->findFilteredQuery($filter, $orderBy, $limit, $offset, $fields), array(), $this->cacheTime);
		$acuire = str_replace('Mapper', '', get_class($this)).'Collection';
		$collection = new $acuire($result, $this);
		foreach ($collection as $object){
			$this->addToMap($object);
		}
		$collection->rewind();
		return $collection;
	}
	
	/**
	 * Zwraca kolekcje obiektow na podstawie zapytania findAllQuery
	 *
	 * @return ModelCollection
	 */
	function findFilteredPaged($filter = '', $orderBy = '', $limit = null, $offset = null, $fields = ''){
		$result = $this->doStatement($this->findFilteredPagedQuery($filter, $orderBy, $limit, $offset, $fields), array(), $this->cacheTime);
		$acuire = str_replace('Mapper', '', get_class($this)).'Collection';
		$collection = new $acuire($result, $this);
		foreach ($collection as $object){
			$this->addToMap($object);
		}
		$collection->rewind();
		return $collection;
	}
	
	function findAll($filter = '', $orderBy = ''){
		return $this->findFiltered($filter, $orderBy);
	}

	/**
	 * Zwraca kolekcje obiektow na podstawie zadanego zapytania
	 *
	 * @param unknown_type $stmt
	 * @return ModelCollection
	 */
	function findMany($stmt){
		
		$result = $this->doStatement($stmt, array(), $this->cacheTime);
		$acuire = str_replace('Mapper', '', get_class($this)).'Collection';
		$collection = new $acuire($result, $this);
		foreach ($collection as $object){
			//debug($object);
			$this->addToMap($object);
		}
		$collection->rewind();
		return $collection;
	}

	function getCount($filter, $fromView = true){
		if ($fromView == false){
			$from = $this->tableName;
		} else {
			$from = $this->selectViewName;
		}
		
		$query = "
			SELECT COUNT(id)
			FROM ".$from."
			".$filter."
		";
		
		return self::$DB->getOne(self::$DB->prepare($query));
	}

	function getIdentity(){
		$result = self::$DB->getRow($this->mssqlIdentityQuery);
		return $result['id'];
	}
	/**
	 * Metoda doStatement zwraca recordset na podstawie zapytania $stmt i opcjonalnych parametrow $values
	 *
	 * @param string $stmt
	 * @param array $values
	 * @return ADORecordset
	 */
	function doStatement($stmt, $values, $cacheTime = null){
		if (is_null($cacheTime)){
			$result = self::$DB->Execute($stmt, $values);
		} else {
			$result = self::$DB->cacheExecute($cacheTime, $stmt, $values);
		}
		//$this->cacheTime = $this->defaultCacheTime;
		
		return $result;
	}

	/**
	 * Metoda doFind na podstawie parametru id i findQuery -zdefiniowanego w klasach pochodnych, tworzy recordset i przekazuje
	 * go do metody load, ktora na podstawie recordsetu wytwarza obiekt badz zwraca go z mapy tozsamosci
	 *
	 * @param unknown_type $id
	 * @return unknown
	 */
	protected function doFind($id){
		$result = $this->doStatement($this->findQuery, array($id), $this->cacheTime);
		return $this->load($result);
	}
	
	protected function doFindOneFiltered($filter = '', $fields = '', $fromView = true){
		//Database::instance()->getConn()->debug = 1;
		$result = $this->doStatement($this->findOneFilteredQuery($filter, $fields, $fromView), array(), $this->cacheTime);
		
		return $this->load($result);
	}

	/**
	 * Metoda insert - rozwijana w klasach pochodnych
	 * Metoda insert wywolywana jest nie jawnie przez destruktor obiektu ObjectWatcher w przypadku utworzenia modelu bez podania id
	 * Mozliwe utworzenie obiektu bez utrwalania stanu w bazie danych przez ustawienie parametru isGhost na true
	 * @param DomainModel $obj
	 */
	protected abstract function insert(DomainModel $obj);

	/**
	 * Metoda update - rozwijana w klasach pochodnych
	 * Metoda update wywolywana jest nie jawnie przez destruktor obiektu ObjectWatcher w przypadku zmiany jakiegokolwiek parametru w modelu przez wywolanie metody set[Param]
	 * Mozliwe dokonanie zmiany wartosci parametru bez utrwalania stanu w bazie danych przez wywolanie metody $obj->markClean() - na koncu wszystkich set`ow
	 * @param DomainModel $obj
	 */
	protected abstract function update(DomainModel $obj);

	/**
	 * Metoda delete - rozwijana w klasach pochodnych, mozna dolozyc do unit of work, ale z pewna doza ostroznosci
	 *
	 * @param DomainModel $obj
	 */
	protected abstract function delete(DomainModel $obj);

	/**
	 * Metoda doLoad - rozwijana w klasach pochodnych, sluzy do mapowania tabela - obiekt modelu
	 *
	 * @param array $array - wiersz tabeli
	 */
	protected abstract function doLoad($array);
}

?>